package com.kingbook.common.security;

import com.kingbook.common.exception.Exceptions;
import com.kingbook.common.utils.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;

/**
 * 采用对称加密
 * 支持TEA,AES加解密
 */
public class CryptosManager {


    /**
     * 生成TEA密钥,长度为偶数，最小16位
     *
     * @param keySize
     * @return
     */
    public static String genTeaKey(int keySize) {
        int size = keySize <= 16 ? 8 : keySize / 2;
        byte[] bytes = new byte[size];
        new SecureRandom().nextBytes(bytes);
        return EncoderManager.encodeHex(bytes);
    }


    /**
     * 生成AES密钥,可选长度为32,48,64位.
     *
     * @param keySize
     * @return
     */
    public static String genAesKey(int keySize) {
        int size = 128;//aes秘钥生成可选长度128,192,256
        try {
            if (keySize == 32 || keySize == 48 || keySize == 64)
                size = keySize * 4;
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(size);
            SecretKey secretKey = keyGenerator.generateKey();
            return EncoderManager.encodeHex(secretKey.getEncoded());
        } catch (GeneralSecurityException e) {
            throw Exceptions.unchecked(e);
        }
    }

    /**
     * 使用TEA加密原始字符串.返回Hex编码的加密字符串
     *
     * @param plainText 原始输入字符数组
     * @return Hex编码的加密字符串
     */
    public static String teaEncipher(String plainText, String key) {
        TeaEncryption tea = new TeaEncryption(key);
        return tea.encipher(plainText);
    }

    /**
     * 使用TEA解密Hex编码的字符串, 返回原始字符串.
     *
     * @param cipherText Hex编码的加密字符串
     * @return 原始字符串
     */
    public static String teaDecipher(String cipherText, String key) {
        TeaEncryption tea = new TeaEncryption(key);
        return tea.decipher(cipherText);
    }

    /**
     * 使用AES加密原始字符串.返回Hex编码的加密字符串
     *
     * @param plainText 原始输入字符数组
     * @return Hex编码的加密字符串
     */
    public static String aesEncrypt(String plainText, String key) {
        AesEncryption aes = new AesEncryption(key, null);
        return aes.encrypt(plainText);
    }

    /**
     * 使用AES加密原始字符串.返回Hex编码的加密字符串
     *
     * @param plainText 原始输入字符数组
     * @param iv        秘钥偏移量
     * @return Hex编码的加密字符串
     */
    public static String aesEncrypt(String plainText, String key, String iv) {
        AesEncryption aes = new AesEncryption(key, iv);
        return aes.encrypt(plainText);
    }

    /**
     * 使用AES解密Hex编码的字符串, 返回原始字符串.
     *
     * @param cipherText Hex编码的加密字符串
     * @return 原始字符串
     */
    public static String aesDecipher(String cipherText, String key) {
        AesEncryption aes = new AesEncryption(key, null);
        return aes.decrypt(cipherText);
    }

    /**
     * 使用AES解密Hex编码的字符串, 返回原始字符串.
     *
     * @param cipherText Hex编码的加密字符串
     * @param iv         秘钥偏移量
     * @return 原始字符串
     */
    public static String aesDecipher(String cipherText, String key, String iv) {
        AesEncryption aes = new AesEncryption(key, iv);
        return aes.decrypt(cipherText);
    }

    /**
     * AES对称加密(取代DES算法)
     * Created by   on 2017/1/12.
     */
    static class AesEncryption {

        private static final String AES = "AES";
        private static final String AES_CBC = "AES/CBC/PKCS5Padding";
        private byte[] ivParameterSpec;
        private byte[] key;


        AesEncryption(String keys, String iv) {
            keys = StringUtils.isEmpty(keys) || keys.length() < 16 ? keys + "o4mn8ng9r2flh9t7" : keys;
            byte[] k = keys.getBytes();
            key = new byte[]{k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7], k[8], k[9], k[10], k[11], k[12], k[13], k[14], k[15]};

            iv = StringUtils.isEmpty(iv) || iv.length() < 16 ? iv + "1741af8b96bcc4e4" : iv;
            byte[] v = iv.getBytes();
            ivParameterSpec = new byte[]{v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10], v[11], v[12], v[13], v[14], v[15]};
        }

        /**
         * 使用AES加密原始字符串.
         *
         * @param plainText 原始输入字符数组
         * @return Hex编码的加密字符串
         */
        String encrypt(String plainText) {
            return EncoderManager.encodeHex(aes(StringUtils.getBytes(plainText), key, ivParameterSpec, Cipher.ENCRYPT_MODE));
        }

        /**
         * 使用AES解密字符串, 返回原始字符串.
         *
         * @param cipherText Hex编码的加密字符串
         * @return 原始字符串
         */
        String decrypt(String cipherText) {
            return StringUtils.toString(aes(EncoderManager.decodeHex(cipherText), key, ivParameterSpec, Cipher.DECRYPT_MODE));
        }

        /**
         * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.
         *
         * @param input      原始字节数组
         * @param key        符合AES要求的密钥
         * @param iv         初始向量
         * @param cipherMode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
         */
        private byte[] aes(byte[] input, byte[] key, byte[] iv, int cipherMode) {
            try {
                SecretKey secretKey = new SecretKeySpec(key, AES);
                Cipher cipher = Cipher.getInstance(AES_CBC);
                cipher.init(cipherMode, secretKey, new IvParameterSpec(iv));
                return cipher.doFinal(input);
            } catch (GeneralSecurityException e) {
                throw Exceptions.unchecked(e);
            }
        }


    }


    /**
     * TEA对称加密
     * Created by   on 2017/1/11.
     */
    static class TeaEncryption {

        private long UINT32_MAX = 0xFFFFFFFFL;
        private long BYTE_1 = 0xFFL, BYTE_2 = 0xFF00L, BYTE_3 = 0xFF0000L, BYTE_4 = UINT32_MAX;
        private long delta = 0x9E3779B9L;
        private int glen;
        private int loops;
        private byte[] key;


        /**
         * 默认每次操作处理8个字节，加解密轮数为16，KEY最小16字节
         * 加密解密轮数应为8的倍数，推荐加密轮数为64轮
         */
        TeaEncryption(String keys) {
            keys = StringUtils.isEmpty(keys) || keys.length() < 16 ? keys + "2bns5fpwFme3N7re" : keys;
            byte[] k = keys.getBytes();
            glen = 8;
            loops = 32;//encryptTime < 16 ? 16 : (encryptTime / 8) * 8;
            key = new byte[]{k[0], k[1], k[2], k[3],
                    k[4], k[5], k[6], k[7],
                    k[8], k[9], k[10], k[11],
                    k[12], k[13], k[14], k[15]};
        }

        /**
         * 使用TEA加密原始字符串.
         *
         * @param plainText 原始字符串
         * @return Hex编码的加密字符串
         */
        String encipher(String plainText) {
            return EncoderManager.encodeHex(encipher(StringUtils.getBytes(plainText)));
        }

        /**
         * 使用TEA解密字符串, 返回原始字符串.
         *
         * @param cipherText Hex编码的加密字符串
         * @return 原始字符串
         */
        String decipher(String cipherText) {
            return StringUtils.toString(decipher(EncoderManager.decodeHex(cipherText)));

        }

        /**
         * 使用TEA加密无编码的原始字节数组, 返回无编码的字节数组结果.
         *
         * @param data 需要加密的明文
         * @return 加密后的密文
         */
        byte[] encipher(byte[] data) {
            if (data == null || data.length <= 0)
                throw new RuntimeException("加密数据不能为空");
            byte[] plain = new byte[glen];
            byte[] encPlain;
            int data_len = data.length;
            int multiple = data_len / glen;
            int residues = data_len % glen;
            if (residues > 0) {
                multiple += 1;
            }
            byte[] result = new byte[multiple * glen + 1];
            result[0] = (byte) residues;
            for (int m = 0; m < multiple; m++) {
                for (int i = 0; i < glen; i++) {
                    if (m * glen + i < data_len)
                        plain[i] = data[m * glen + i];
                }
                encPlain = encrypt(plain);
                for (int j = 0; j < glen; j++) {
                    result[m * glen + j + 1] = encPlain[j];
                }
            }
            return result;
        }

        /**
         * 使用TEA解密无编码的原始字节数组, 返回无编码的字节数组结果.
         *
         * @param data 需要解密的密文
         * @return 解密后的明文
         */
        byte[] decipher(byte[] data) {
            if (data == null || data.length <= 0)
                throw new RuntimeException("密文不能为空");
            if (data.length % glen != 1)
                throw new RuntimeException("密文不符合规范");
            byte[] plain = new byte[glen];
            byte[] desPlain;
            int result_len = data.length - 1;
            int multiple = result_len / glen;
            int residues = (int) (data[0]);
            if (residues > 0)
                result_len = (result_len - glen) + residues;
            byte[] result = new byte[result_len];

            for (int m = 0; m < multiple; m++) {
                for (int i = 0; i < glen; i++) {
                    plain[i] = data[m * glen + i + 1];
                }
                desPlain = decrypt(plain);
                for (int i = 0, rLen; i < glen; i++) {
                    rLen = m * glen + i;
                    if (rLen < result.length)
                        result[rLen] = desPlain[i];
                }
            }
            return result;
        }

        private byte[] encrypt(byte[] v) {
            v = fillByte(v);
            long left = bytes_to_uint32(new byte[]{v[0], v[1], v[2], v[3]});
            long right = bytes_to_uint32(new byte[]{v[4], v[5], v[6], v[7]});
            long a = key[0], b = key[1], c = key[2], d = key[3], sum = 0L;
            for (int i = 0; i < loops; i++) {
                sum = toUInt32(sum + delta);
                left = toUInt32(left + toUInt32(toUInt32(toUInt32(right << 4) + a) ^ toUInt32(right + sum) ^ toUInt32((right >> 5) + b)));
                right = toUInt32(right + toUInt32(toUInt32(toUInt32(left << 4) + c) ^ toUInt32(left + sum) ^ toUInt32((left >> 5) + d)));
            }
            return long_to_bytes(left, right, true);
        }

        private byte[] decrypt(byte[] v) {
            long left = bytes_to_uint32(new byte[]{v[0], v[1], v[2], v[3]});
            long right = bytes_to_uint32(new byte[]{v[4], v[5], v[6], v[7]});
            long a = key[0], b = key[1], c = key[2], d = key[3], sum = 0xC6EF3720L;
            for (int i = 0; i < loops; i++) {
                right = toUInt32(right - toUInt32(toUInt32(toUInt32(left << 4) + c) ^ toUInt32(left + sum) ^ toUInt32((left >> 5) + d)));
                left = toUInt32(left - toUInt32(toUInt32(toUInt32(right << 4) + a) ^ toUInt32(right + sum) ^ toUInt32((right >> 5) + b)));
                sum = toUInt32(sum - delta);
            }
            return long_to_bytes(left, right, true);
        }

        private byte[] fillByte(byte[] v) {
            if (v != null && v.length != 8) {
                byte[] nv = new byte[8];
                for (int i = 0; i < 8; i++)
                    if (i < v.length)
                        nv[i] = v[i];
                    else
                        nv[i] = (byte) 0x00;
                return nv;
            }
            return v;
        }

        private long bytes_to_uint32(byte[] bs) {
            return ((bs[0] << 24) & BYTE_4) + ((bs[1] << 16) & BYTE_3)
                    + ((bs[2] << 8) & BYTE_2) + (bs[3] & BYTE_1);
        }

        private byte[] long_to_bytes(long n1, long n2, boolean isLowPosition) {
            byte a1 = (byte) ((n1 & BYTE_4) >> 24);
            byte b1 = (byte) ((n1 & BYTE_3) >> 16);
            byte c1 = (byte) ((n1 & BYTE_2) >> 8);
            byte d1 = (byte) (n1 & BYTE_1);
            byte a2 = (byte) ((n2 & BYTE_4) >> 24);
            byte b2 = (byte) ((n2 & BYTE_3) >> 16);
            byte c2 = (byte) ((n2 & BYTE_2) >> 8);
            byte d2 = (byte) (n2 & BYTE_1);
            if (isLowPosition)
                return new byte[]{a1, b1, c1, d1, a2, b2, c2, d2};
            byte ha1 = (byte) (n1 >> 56);
            byte hb1 = (byte) ((n1 >> 48) & BYTE_1);
            byte hc1 = (byte) ((n1 >> 40) & BYTE_1);
            byte hd1 = (byte) ((n1 >> 32) & BYTE_1);
            byte ha2 = (byte) (n2 >> 56);
            byte hb2 = (byte) ((n2 >> 48) & BYTE_1);
            byte hc2 = (byte) ((n2 >> 40) & BYTE_1);
            byte hd2 = (byte) ((n2 >> 32) & BYTE_1);
            return new byte[]{ha1, hb1, hc1, hd1, a1, b1, c1, d1, ha2, hb2, hc2, hd2, a2, b2, c2, d2};
        }

        private long toUInt32(long n) {
            return n & UINT32_MAX;
        }


    }
}
